package mx.com.crowdgine.display.jogl;

import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GL4bc;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.glu.GLU;

import mx.com.crowdgine.assetloader.Asset;
import mx.com.crowdgine.assetloader.OBJModel;
import mx.com.crowdgine.assetloader.OBJModelAsset;
import mx.com.crowdgine.assetloader.Texture;
import mx.com.crowdgine.assetloader.TextureRectangle;
import mx.com.crowdgine.camera.Camera;
import mx.com.crowdgine.camera.ScriptedCamera;
import mx.com.crowdgine.crowds.CrowdSystem;
import mx.com.crowdgine.crowds.samples.SimpleCrowds;
import mx.com.crowdgine.display.Renderer;
import mx.com.crowdgine.display.animation.Animation;
import mx.com.crowdgine.display.animation.AnimationFrame;
import mx.com.crowdgine.display.jogl.shader.ShaderObject;
import mx.com.crowdgine.entity.Entity;
import mx.com.crowdgine.particlesystem.ParticleSystem;
import mx.com.crowdgine.particlesystem.jogl.SineWaveParticleSystem;
import mx.com.crowdgine.particlesystem.jogl.SnowParticleSystem;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.util.Animator;

public class JoglRenderer implements Renderer, GLEventListener {
	// Constants
	final int DEFAULT_WIDTH = 640;
	final int DEFAULT_HEIGHT = 480;
	final String DEFAULT_FRAME_NAME = "Application";

	// Flags
	protected boolean initializedDrawables = false;

	// Window attributes
	private int width;
	private int height;

	// Cameras
	private List<Camera> cameras = new ArrayList<Camera>();
	private Camera activeCamera;

	// Renderer stuff
	private GLProfile glp;
	private GLCapabilities caps;
	private java.awt.Frame frame;
	protected GLCanvas canvas;

	float dt;
	long lastTime;
	float activeTime = 0;
	
	String originalTitle;
	
	private FloatBuffer colorBuffer;
	private FloatBuffer vertexBuffer;
	private FloatBuffer textureBuffer;

	private int currentActiveTexture;
	
	GLAutoDrawable drawableImplementation;

	public Animator animator;
	
	
	

	// Objects to be rendered
	protected List<Entity> entities = new ArrayList<Entity>();
	private List<ParticleSystem> particleSystems = new ArrayList<ParticleSystem>();
	private List<Asset> assets = new ArrayList<Asset>();
	private List<CrowdSystem> crowdSystems = new ArrayList<CrowdSystem>();

	public JoglRenderer() {
		GLProfile.initSingleton(true);
		glp = getLatestGLProfile();
		caps = new GLCapabilities(glp);
		canvas = new GLCanvas(caps);
		canvas.addGLEventListener(this);

		frame = new Frame(DEFAULT_FRAME_NAME);
		frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
		frame.add(canvas);
		frame.setVisible(true);

		// by default, an AWT Frame doesn't do anything when you click
		// the close button; this bit of code will terminate the program when
		// the window is asked to close
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
        
	}
	
	
	public JoglRenderer(String frameName,int width, int height) {
		GLProfile.initSingleton(true);
		glp = getLatestGLProfile();
		caps = new GLCapabilities(glp);
		canvas = new GLCanvas(caps);
		canvas.addGLEventListener(this);

		frame = new Frame(frameName);
		frame.setSize(width, height);
		frame.add(canvas);
		frame.setVisible(true);

		// by default, an AWT Frame doesn't do anything when you click
		// the close button; this bit of code will terminate the program when
		// the window is asked to close
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
        
	}
	
	
	public GLProfile getLatestGLProfile() {
		int glVersion = 4;
		while (glVersion > 2) {
			if (GLProfile.get("GL" + glVersion + "bc") != null) {
				System.out.println("GLPROFILE: GL" + glVersion + "bc");
				return GLProfile.get("GL" + glVersion + "bc");
			}
			glVersion--;
		}
		System.out.println("GLPROFILE: GL2");
		return GLProfile.getDefault();
	}

	public void beginRenderingLoop() {
		Animator animator = new Animator(canvas);
		animator.start();
	}

	/**
	 * Updates the current drawable implementation, calls all the update logic
	 * and displays everything on screen.
	 * 
	 * @param drawable
	 *            This instance of drawable is automatically created.
	 */
	public void display(GLAutoDrawable drawable) {
		this.drawableImplementation = drawable;
		update(drawable);
		render(drawable);
	}

	protected void update(GLAutoDrawable drawable) {
		
		if(this.originalTitle == null) {
			this.originalTitle = frame.getTitle();
		}
		
		dt = (float)(System.currentTimeMillis() - lastTime) / 1000.0f;
        lastTime = System.currentTimeMillis();
        activeTime += 1;
		frame.setTitle(this.originalTitle + " - " + 1.0f/dt + " fps");
	}

	/**
	 * Checks the current profile and returns the corresponding GL
	 * implementation.
	 * 
	 * @param drawable
	 *            (This variable is automatically generated)
	 * @return The best GL implementation in the machine, casted to a GL
	 */
	protected GL4bc getBestGlImplementation(GLAutoDrawable drawable) {
		if (this.glp.isGL4bc()) {
			return drawable.getGL().getGL4bc();
		}
		if (this.glp.isGL3bc()) {
			return (GL4bc) drawable.getGL().getGL3bc();
		}
		return (GL4bc) drawable.getGL().getGL2();
	}

	/**
	 * The main renderer loop.
	 * 
	 * @param drawable
	 *            This instance of drawable is automatically created.
	 */
	private void render(GLAutoDrawable drawable) {
		
		GL4bc gl = getBestGlImplementation(drawable);

		// ensure pipeline is clean before doing particle system work
		gl.glFinish();

		setupCamera(gl);
		// do rendering part for each crowd system
		renderCrowdSystems(gl);
		
		
		// render all entities
		renderEntities(gl);

		// render all assets
		renderAssets(gl);

		// do rendering part for each particle system
		renderParticleSystems(gl);

		

		gl.glFlush();
	}


	public void setupCamera(GL4bc gl) {
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
		moveCamera(gl);
	}


	public void moveCamera(GL4bc gl) {
		gl.glLoadIdentity();

		gl.glViewport(0, 0, frame.getWidth(), frame.getHeight());
		
		GLU glu = new GLU();
		glu.gluPerspective(45, (float) this.width / (float) this.height, 1, 10000);

		this.activeCamera.interact(gl);
		if(this.activeCamera instanceof ScriptedCamera)
			((ScriptedCamera) this.activeCamera).setTime(activeTime);
	}


	public void cleanupMemory() {
		long freemem = Runtime.getRuntime().freeMemory();
        long maxmem = Runtime.getRuntime().maxMemory();
        long totmem = Runtime.getRuntime().totalMemory();
        long mbFactor = 1024*1024;
        if(freemem < mbFactor) {
        	printMemory(freemem, maxmem, totmem, mbFactor);
        	System.out.println("Running garbage collection");
        	
        	freemem = Runtime.getRuntime().freeMemory();
            maxmem = Runtime.getRuntime().maxMemory();
            totmem = Runtime.getRuntime().totalMemory();
            
            printMemory(freemem, maxmem, totmem, mbFactor);
        	
        	System.gc();
        }
	}


	public void printMemory(long freemem, long maxmem, long totmem,
			long mbFactor) {
		System.out.println("---------------------------------------------");
		System.out.println("Free Memory (MB): " + (float)freemem / ((float)mbFactor));
		System.out.println("Max Memory (MB): " + (float)maxmem / ((float)mbFactor));
		System.out.println("Total Memory (MB): " + (float)totmem / ((float)mbFactor));
		System.out.println("---------------------------------------------");
	}


	/**
	 * Renders all the particle systems.
	 * 
	 * @param The
	 *            GL implementation to use for rendering
	 */
	private void renderParticleSystems(GL4bc gl) {
		for (ParticleSystem particleSystem : this.particleSystems) {
			// particle system result is in a vertex object id in gpu
			if(particleSystem instanceof SnowParticleSystem){
				gl.glPointSize(5.0f);
				gl.glColor3f(1.0f, 1.0f, 1.0f);
			}
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, particleSystem.getVertexObjectId(SineWaveParticleSystem.VERTICES_NAME));
			gl.glVertexPointer(4, GL2.GL_FLOAT, 0, 0);
			gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
			gl.glDrawArrays(GL2.GL_POINTS, 0, particleSystem.getArraySize());
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
			gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
		}
	}

	/**
	 * Renders all the crowd systems.
	 * 
	 * @param The
	 *            GL implementation to use for rendering
	 */
	private void renderCrowdSystems(GL4bc gl){
        // do rendering part for each crowd system
        for(CrowdSystem crowd : this.crowdSystems){
        	if(crowd.isInitialized()){
        		
        		FrameBuffer frameBuffer = renderCrowdPosFBO(gl, crowd);
                
                moveCamera(gl);
                
                renderCrowd(gl, crowd, frameBuffer);
        		//renderCrowdVertices(gl, crowd);
                
        	}
        }
	}


	public void renderCrowd(GL4bc gl, CrowdSystem crowd, FrameBuffer frameBuffer) {
		ShaderObject particleShader = crowd.getParticleShader();
		if(particleShader == null) {
			particleShader = new ShaderObject(gl, "Assets/Shaders/particleVertex.glsl", "Assets/Shaders/particleFragment.glsl");
//			particleShader = new ShaderObject(gl, "Assets/Shaders/mocapVertex2.glsl", "Assets/Shaders/mocapFragment2.glsl");
			crowd.setParticleShader(particleShader);
		}
		
		
		// debug texture
		gl.glEnable(GL4bc.GL_TEXTURE_RECTANGLE);
		gl.glBindTexture(GL4bc.GL_TEXTURE_RECTANGLE, frameBuffer.getTextureId());
		gl.glBegin(GL4bc.GL_QUADS);
			gl.glTexCoord2f(frameBuffer.getWidth(), frameBuffer.getHeight());
			gl.glVertex3f(1, 1, 0);
			
			gl.glTexCoord2f(0, frameBuffer.getHeight());
			gl.glVertex3f(0, 1, 0);
			
			gl.glTexCoord2f(0, 0);
			gl.glVertex3f(0, 0, 0);
			
			gl.glTexCoord2f(frameBuffer.getWidth(), 0);
			gl.glVertex3f(1, 0, 0);
		gl.glEnd();
		
		
		gl.glBindTexture(GL4bc.GL_TEXTURE_RECTANGLE, 0);
//		gl.glDisable(GL4bc.GL_TEXTURE_RECTANGLE);
		
		
		gl.glActiveTexture(GL4bc.GL_TEXTURE1);
		gl.glBindTexture(GL4bc.GL_TEXTURE_RECTANGLE, frameBuffer.getTextureId());
		
//		particleShader.activate();
//		particleShader.setUniformi("crowdPosTexture", 1);
//		particleShader.setUniformf("width", frameBuffer.getWidth());
//		particleShader.setUniformf("height", frameBuffer.getHeight());
//		
		for(Asset asset : crowd.getAssets()) {
			
			if(asset.getAnimation() != null) {
				asset.getAnimation().setParameter("crowdPosTexture", new Integer(1));
				asset.getAnimation().setParameter("width", new Float(frameBuffer.getWidth()));
				asset.getAnimation().setParameter("height", new Float(frameBuffer.getHeight()));
			}
			
			renderAnimatedAsset(gl, crowd.getCrowdSize(), asset);
			
//			renderAsset(gl, asset, crowd.getCrowdSize());
		}
		
//		particleShader.deactivate();
		
		gl.glBindTexture(GL4bc.GL_TEXTURE_RECTANGLE, 0);
		gl.glActiveTexture(0);
		
	}


	public FrameBuffer renderCrowdPosFBO(GL4bc gl, CrowdSystem crowd) {
		FrameBuffer frameBuffer = crowd.getFrameBuffer();
		
		if(!frameBuffer.isInitialized()) {
			
			initCrowdFBO(gl, crowd);
		}
		
		
		renderFBO(gl, crowd);
		
		return frameBuffer;
	}


	public void renderFBO(GL4bc gl, CrowdSystem crowd) {
		
		FrameBuffer frameBuffer = crowd.getFrameBuffer();
		
		gl.glBindFramebuffer(GL4bc.GL_FRAMEBUFFER, frameBuffer.getId());
		gl.glViewport(0, 0, frameBuffer.getWidth(), frameBuffer.getHeight());
		gl.glClear(GL4bc.GL_COLOR_BUFFER_BIT | GL4bc.GL_DEPTH_BUFFER_BIT);
		
		gl.glPushMatrix();
		gl.glLoadIdentity();
		
		ShaderObject crowdPosShader = crowd.getCrowdPosShader();
		
		if(crowdPosShader == null) {
			crowdPosShader = new ShaderObject(gl, "Assets/Shaders/crowdPosVertex.glsl", "Assets/Shaders/crowdPosFragment.glsl");
			crowd.setCrowdPosShader(crowdPosShader);
		}
		
		crowdPosShader.activate();
		
		crowdPosShader.setUniformf("width", frameBuffer.getWidth());
		crowdPosShader.setUniformf("height", frameBuffer.getHeight());
		
		renderCrowdVertices(gl, crowd);
		
		crowdPosShader.deactivate();
		
		gl.glPopMatrix();
		
		gl.glBindFramebuffer(GL4bc.GL_FRAMEBUFFER, 0);
	}

	
	private void errCheck(String errMsg, GL4bc gl) {
		int errorcode = gl.glGetError();
		
		GLU glu = new GLU();
		
		if(errorcode != GL4bc.GL_NO_ERROR) {
			String errorString = glu.gluErrorString(errorcode);
			System.err.println(errMsg + " - OpenGL Error: " + errorString);
//			throw new RuntimeException(errMsg + " - OpenGL Error: " + errorString);
		} else {
			System.out.println("No OpenGL Errors found.");
		}
	}
	
	

	public void initCrowdFBO(GL4bc gl, CrowdSystem crowd) {
		
		FrameBuffer frameBuffer = crowd.getFrameBuffer();
		
		frameBuffer.setWidth((int)(Math.ceil(Math.sqrt(crowd.getCrowdSize()))));
		frameBuffer.setHeight((int)(Math.ceil(Math.sqrt(crowd.getCrowdSize()))));
		
		IntBuffer textureIdBuffer = Buffers.newDirectIntBuffer(1);
		gl.glGenTextures(1, textureIdBuffer);
		errCheck("Generating Texture.", gl);
		
		frameBuffer.setTextureId(textureIdBuffer.get(0));
		gl.glBindTexture(GL4bc.GL_TEXTURE_RECTANGLE, frameBuffer.getTextureId());
		errCheck("Binding Texture.", gl);
		
		gl.glTexParameteri(GL4bc.GL_TEXTURE_RECTANGLE, GL4bc.GL_TEXTURE_MIN_FILTER, GL4bc.GL_NEAREST);
		gl.glTexParameteri(GL4bc.GL_TEXTURE_RECTANGLE, GL4bc.GL_TEXTURE_MAG_FILTER, GL4bc.GL_NEAREST);
		gl.glTexParameteri(GL4bc.GL_TEXTURE_RECTANGLE, GL4bc.GL_TEXTURE_WRAP_S, GL4bc.GL_CLAMP);
		gl.glTexParameteri(GL4bc.GL_TEXTURE_RECTANGLE, GL4bc.GL_TEXTURE_WRAP_T, GL4bc.GL_CLAMP);
		gl.glTexImage2D(GL4bc.GL_TEXTURE_RECTANGLE, 0, GL4bc.GL_RGBA32F, frameBuffer.getWidth(), frameBuffer.getHeight(), 0, GL4bc.GL_RGBA, GL4bc.GL_FLOAT, null);
		errCheck("Initializing Texture.", gl);
		
		
		IntBuffer fboIdBuffer = Buffers.newDirectIntBuffer(1);
		gl.glGenFramebuffers(1, fboIdBuffer);
		errCheck("Generating framebuffer.", gl);
		frameBuffer.setId(fboIdBuffer.get(0));
		
		gl.glBindFramebuffer(GL4bc.GL_FRAMEBUFFER, frameBuffer.getId());
		errCheck("Binding framebuffer.", gl);
		
//		gl.glDrawBuffer(GL4bc.GL_FRONT_AND_BACK);
//		errCheck("Setting draw buffer.", gl);
		
		
		IntBuffer renderBufferIdBuffer = Buffers.newDirectIntBuffer(1);
		gl.glGenRenderbuffers(1, renderBufferIdBuffer);
		errCheck("Generating render buffers.", gl);
		frameBuffer.setRenderBufferId(renderBufferIdBuffer.get(0));
		gl.glBindRenderbuffer(GL4bc.GL_RENDERBUFFER, frameBuffer.getRenderBufferId());
		errCheck("Binding render buffer.", gl);
		gl.glRenderbufferStorage(GL4bc.GL_RENDERBUFFER, GL4bc.GL_DEPTH_COMPONENT16, frameBuffer.getWidth(), frameBuffer.getHeight());
		errCheck("Specifiyng render buffer storage.", gl);
		gl.glFramebufferTexture2D(GL4bc.GL_FRAMEBUFFER, GL4bc.GL_COLOR_ATTACHMENT0, GL4bc.GL_TEXTURE_RECTANGLE, frameBuffer.getTextureId(), 0);
		// gl.glFramebufferTexture(GL4bc.GL_FRAMEBUFFER, GL4bc.GL_COLOR_ATTACHMENT0, frameBuffer.getTextureId(), 0);
		errCheck("Binding texture to FBO.", gl);
		
		int fboStatus = gl.glCheckFramebufferStatus(GL4bc.GL_FRAMEBUFFER);
		if(fboStatus != GL4bc.GL_FRAMEBUFFER_COMPLETE) {
			System.err.println("Framebuffer not complete, cannot use FBO.");
		}
		
		
		gl.glFramebufferRenderbuffer(GL4bc.GL_FRAMEBUFFER, GL4bc.GL_DEPTH_ATTACHMENT, GL4bc.GL_RENDERBUFFER, frameBuffer.getRenderBufferId());
		
		frameBuffer.setInitialized(true);
		
		gl.glBindTexture(GL4bc.GL_TEXTURE_RECTANGLE, 0);
		gl.glBindRenderbuffer(GL4bc.GL_RENDERBUFFER, 0);
		gl.glBindFramebuffer(GL4bc.GL_FRAMEBUFFER, 0);
	}


	public void renderCrowdVertices(GL4bc gl, CrowdSystem crowd) {
		gl.glPointSize(3.0f);
		gl.glColor3f(1.0f, 0.0f, 0.0f);
		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, crowd.getVertexObjectId(SimpleCrowds.AGENTS_NAME));
		gl.glVertexPointer(4, GL2.GL_FLOAT, 0, 0);
		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glDrawArrays(GL2.GL_POINTS, 0, crowd.getCrowdSize());
		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
		gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glPointSize(1.0f);
	}

	/**
	 * Renders all the assets
	 * 
	 * @param The
	 *            GL implementation to use for rendering
	 */
	private void renderAssets(GL4bc gl) {
		iterateAndRenderAssets(gl, assets, 1);
	}


	public void iterateAndRenderAssets(GL4bc gl, List<Asset> assets, int numInstances) {
		for(Asset asset : assets) {
	    	renderAnimatedAsset(gl, numInstances, asset);
	    }
	}


	public void renderAnimatedAsset(GL4bc gl, int numInstances, Asset asset) {
		switch(asset.getAssetType()) {
			case Asset.OBJ_MODEL_TYPE:
				if(!asset.isLoaded()){
					((OBJModelAsset)asset).load(gl);
				}
				
				Animation animation = asset.getAnimation();
				ShaderObject shaderObject = null;
				
				if(animation != null) {
					
					if(animation.getShaderObject() == null) {
						if(animation.getVertexShader() == null) {
							if(animation.getDynamicParameters().isEmpty())
								shaderObject = new ShaderObject(gl, animation.getFragmentShader());
							else
								shaderObject = new ShaderObject(gl, animation.getFragmentShader(), animation.getDynamicParameters());
						} else {
							if(animation.getDynamicParameters().isEmpty())
								shaderObject = new ShaderObject(gl, animation.getVertexShader(), animation.getFragmentShader());
							else
								shaderObject = new ShaderObject(gl, animation.getVertexShader(), animation.getFragmentShader(), animation.getDynamicParameters());
						}
						
						animation.setShaderObject(shaderObject);
						
					} else {
						shaderObject = animation.getShaderObject();
					}
					
					
//					renderJoints(gl, animation);
//	    				renderAsset(gl, asset);
					
					shaderObject.activate();
//					errCheck("Activating shader", gl);
					setShaderParams(shaderObject, animation);
//					errCheck("Setting shader params", gl);
					
				}
				
				renderAsset(gl, asset, numInstances);
				
//	    			renderJoints(gl, animation);
				
				if(shaderObject != null) {
					clearShaderParams(shaderObject, animation);
					shaderObject.deactivate();
//					errCheck("Deactivating shader", gl);
					updateAnimationIndex(animation);
				}
				
				break;
		}
	}


	public void renderAsset(GL2 gl, Asset asset, int numInstances) {
//		((OBJModel)asset.getRelatedAsset()).draw(gl);
		
		/*float LightAmbient[]  = { 0.4f, 0.4f, 0.4f, 1.0f };
	    float LightDiffuse[]  = { 0.5f, 0.5f, 0.5f, 1.0f };
	    float LightPosition[] = { 320.0f, 200.0f, 240.0f, 0.0f };
	    float LightSpecular[] = { 0.3f, 0.3f, 0.3f, 1.0f };
	    float MaterialSpecular[] = { 1.0f, 1.0f, 1.0f, 1.0f};

	    gl.glEnable(gl.GL_LIGHTING);
	    gl.glEnable(gl.GL_LIGHT1);
	    gl.glLightfv(gl.GL_LIGHT1, gl.GL_AMBIENT, Buffers.newDirectFloatBuffer(LightAmbient));        // Setup The Ambient Light
	    gl.glLightfv(gl.GL_LIGHT1, gl.GL_DIFFUSE, Buffers.newDirectFloatBuffer(LightDiffuse));        // Setup The Diffuse Light
	    gl.glLightfv(gl.GL_LIGHT1, gl.GL_SPECULAR, Buffers.newDirectFloatBuffer(LightSpecular));    // Setup The Diffuse Light
	    gl.glLightfv(gl.GL_LIGHT1, gl.GL_POSITION,Buffers.newDirectFloatBuffer(LightPosition));    // Position The Light

	    gl.glEnable(gl.GL_COLOR_MATERIAL);
	    gl.glColorMaterial(gl.GL_FRONT_AND_BACK, gl.GL_AMBIENT_AND_DIFFUSE);
	    gl.glColor3f(1.0f, 1.0f, 1.0f);
	    gl.glEnable(gl.GL_DEPTH_TEST);*/
		
		if(!asset.isLoaded()){
			((OBJModelAsset)asset).load(gl);
		}
		
		OBJModel model = ((OBJModel)asset.getRelatedAsset());
		
		if(!model.isGlBuffersInitialized()) {
			IntBuffer indexIdBuffer = Buffers.newDirectIntBuffer(1);
			gl.glGenBuffers(1, indexIdBuffer);
			int indexId = indexIdBuffer.get(0);
			model.setIndexBufferId(indexId);
			
			gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indexId);
			gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, model.getIndexCount()*Integer.SIZE/8, model.getIndexBuffer(), GL2.GL_STATIC_DRAW);
			gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, 0);
			
			IntBuffer vertexIdBuffer = Buffers.newDirectIntBuffer(1);
			gl.glGenBuffers(1, vertexIdBuffer);
			int vertexId = vertexIdBuffer.get(0);
			model.setVertexBufferId(vertexId);
			
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, model.getVertexBufferId());
			gl.glBufferData(GL2.GL_ARRAY_BUFFER, model.getVerts().size()*3*Float.SIZE/8, model.getVertexBuffer(), GL2.GL_STATIC_DRAW);
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
			
			IntBuffer normalIdBuffer = Buffers.newDirectIntBuffer(1);
			gl.glGenBuffers(1, normalIdBuffer);
			int normalId = normalIdBuffer.get(0);
			model.setNormalBufferId(normalId);
			
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, model.getNormalBufferId());
			gl.glBufferData(GL2.GL_ARRAY_BUFFER, model.getNormalListCount()*3*Float.SIZE/8, model.getNormalBuffer(), GL2.GL_STATIC_DRAW);
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
			
			IntBuffer texCoordIdBuffer = Buffers.newDirectIntBuffer(1);
			gl.glGenBuffers(1, texCoordIdBuffer);
			int texCoordId = texCoordIdBuffer.get(0);
			model.setTexCoordBufferId(texCoordId);
			
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, model.getTexCoordBufferId());
			gl.glBufferData(GL2.GL_ARRAY_BUFFER, model.getTexCoordListCount()*2*Float.SIZE/8, model.getTexCoordBuffer(), GL2.GL_STATIC_DRAW);
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
			
			model.setGlBuffersInitialized(true);
		}
		
		
//		gl.glNormalPointer(GL2.GL_FLOAT, 3*Float.SIZE/8, model.getNormalBuffer());
//		gl.glTexCoordPointer(2, GL2.GL_FLOAT, 3*Float.SIZE/8, model.getTexCoordBuffer());
		
		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, model.getVertexBufferId());
		gl.glVertexPointer(3, GL2.GL_FLOAT, 3*Float.SIZE/8, 0);
		
		
		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, model.getNormalBufferId());
		gl.glNormalPointer(GL2.GL_FLOAT, 3*Float.SIZE/8, 0);
		
		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, model.getTexCoordBufferId());
		gl.glTexCoordPointer(2, GL2.GL_FLOAT, 2*Float.SIZE/8, 0);
		
		
		gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, model.getIndexBufferId());
		
		// gl.glVertexPointer(3, GL2.GL_FLOAT, 12, null);
		
		
		
		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);
		gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
		
//		gl.glNormalPointer(GL2.GL_FLOAT, 4, model.getNormalBuffer());
//		gl.glTexCoordPointer(3, GL2.GL_FLOAT, 4, model.getTexCoordBuffer());
		
		gl.glDrawElementsInstanced(GL2.GL_TRIANGLES, model.getIndexCount(), GL2.GL_UNSIGNED_INT, null, numInstances);
		
		gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
		gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
		gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
		
		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
		gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, 0);
	}


	public void renderJoints(GL2 gl, Animation animation) {
		if(animation.getParameter("jointList") != null) {
			FloatBuffer jointList = (FloatBuffer) animation.getParameter("jointList");
			jointList.rewind();
			gl.glPointSize(3.0f);
			gl.glColor3f(1.0f, 0.0f, 0.0f);
			gl.glBegin(gl.GL_POINTS);
			for(int i = 0; i < animation.getParameterLength("jointList") - 3; i+=3) {
//				System.out.println("drawing joint["+i/3+"]: " + jointList[i] +", "+ jointList[i+1]+", "+ jointList[i+2]);
				gl.glVertex3f(jointList.get(i), jointList.get(i+1), jointList.get(i+2));
			}
			gl.glEnd();
		}
	}
	
	
	private void updateAnimationIndex(Animation animation) {
		animation.setAnimationIndex(animation.getAnimationIndex()+1);
		
		if(animation.getAnimationIndex() >= animation.getFrames().size()) {
			animation.setAnimationIndex(0);
		}
		
	}
	
	
	private void initShaderParams(ShaderObject shaderObject, Animation animation) {
		
		for(String parameterName : animation.getParameterNames()) {
			
			Object value = animation.getParameter(parameterName);
			
			if(value instanceof TextureRectangle) {
				
				TextureRectangle texture = (TextureRectangle)value;
				
				shaderObject.getGl().glEnable(GL2.GL_TEXTURE_RECTANGLE);
				
				if(!texture.isInitialized()) {
					
					IntBuffer texIdBuffer = Buffers.newDirectIntBuffer(1);
					shaderObject.getGl().glGenTextures(1, texIdBuffer);
					
					texture.setId(texIdBuffer.get(0));
					
					shaderObject.getGl().glBindTexture(GL2.GL_TEXTURE_RECTANGLE, texture.getId());
					shaderObject.getGl().glTexParameteri(GL2.GL_TEXTURE_RECTANGLE, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
					shaderObject.getGl().glTexParameteri(GL2.GL_TEXTURE_RECTANGLE, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
					shaderObject.getGl().glTexParameteri(GL2.GL_TEXTURE_RECTANGLE, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP);
					shaderObject.getGl().glTexParameteri(GL2.GL_TEXTURE_RECTANGLE, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP);
					
					
					shaderObject.getGl().glTexImage2D(GL2.GL_TEXTURE_RECTANGLE, 0, GL2.GL_RGBA32F, texture.getWidth(), texture.getHeight(), 0, GL2.GL_RGBA, GL2.GL_FLOAT, texture.getData());
					
//					shaderObject.getGl().glTexImage2D(GL2.GL_TEXTURE_RECTANGLE, 0, GL2.GL_RGBA32F, 1, 1, 0, GL2.GL_RGBA, GL2.GL_FLOAT, Buffers.newDirectFloatBuffer(new float[]{1.0f, 2.0f, 3.0f, 0.0f}));
					
					texture.setInitialized(true);
				}
				
				this.currentActiveTexture++;
				this.bindActiveTexture(shaderObject.getGl(), this.currentActiveTexture);
				
				shaderObject.getGl().glBindTexture(GL2.GL_TEXTURE_RECTANGLE, texture.getId());
//				shaderObject.activate();
				shaderObject.setUniformi(parameterName, this.currentActiveTexture);
//				shaderObject.deactivate();
				
			}
		}
		
	}
	
	
	private void setShaderParams(ShaderObject shaderObject, Animation animation) {
//		shaderObject.activate();
		for(String parameterName : animation.getParameterNames()) {
			Object value = animation.getParameter(parameterName);
			
			if(value instanceof Float){
				shaderObject.setUniformf(parameterName, ((Float)value).floatValue());
		//	} else if(value instanceof float[]){
		//		shaderObject.setUniformfv(parameterName, (float []) value, animation.getParameterSize(parameterName), ((float []) value).length / animation.getParameterSize(parameterName));
			} else if(value instanceof FloatBuffer) {
				shaderObject.setUniformfv(parameterName, (FloatBuffer) value, animation.getParameterSize(parameterName), animation.getParameterLength(parameterName) / animation.getParameterSize(parameterName));
			} else if(value instanceof Integer) {
//				shaderObject.activate();
				shaderObject.setUniformi(parameterName, ((Integer)value).intValue());
//				shaderObject.deactivate();
			} else if(value instanceof IntBuffer) {
				shaderObject.setUniformiv(parameterName, (IntBuffer) value, animation.getParameterSize(parameterName), animation.getParameterLength(parameterName) / animation.getParameterSize(parameterName));
//			} else if(value instanceof int[]) {
//				shaderObject.setUniformiv(parameterName, (int []) value, animation.getParameterSize(parameterName), ((int []) value).length / animation.getParameterSize(parameterName));	
			} else if(value instanceof Texture) {
				
				Texture image = (Texture)value;
				
				
				if(!image.isInitialized()) {
					IntBuffer textureIdBuffer = Buffers.newDirectIntBuffer(1);
					shaderObject.getGl().glGenTextures(1, textureIdBuffer);
					errCheck("Generating Texture in Shader Params.", (GL4bc)shaderObject.getGl());
					int textureId = textureIdBuffer.get(0);
					shaderObject.getGl().glBindTexture(GL2.GL_TEXTURE_2D, textureId);
					shaderObject.getGl().glTexParameteri( GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR);
					shaderObject.getGl().glTexParameteri( GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);
					shaderObject.getGl().glTexParameteri( GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP);
					shaderObject.getGl().glTexParameteri( GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP);
					shaderObject.getGl().glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGB, image.getWidth(), image.getHeight(),
						0, GL2.GL_RGB, GL2.GL_UNSIGNED_BYTE, image.getPixels());
					image.setId(textureId);
					image.setInitialized(true);
				}
				
				shaderObject.getGl().glActiveTexture(GL2.GL_TEXTURE0);
				shaderObject.getGl().glBindTexture(GL2.GL_TEXTURE_2D, image.getId());
				shaderObject.setUniformi(parameterName, 0);
				
				
			}
			// TODO: implement other types
		}
//		shaderObject.deactivate();
		
		setAnimationParams(shaderObject, animation);
		
	}


	public void setAnimationParams(ShaderObject shaderObject,
			Animation animation) {
		if(animation.getFrames().size() > 0) {
			AnimationFrame frame = animation.getFrames().get(animation.getAnimationIndex());
			shaderObject.setUniformfv("animationData", frame.getData(), frame.getDataSize(), frame.getDataLength() / frame.getDataSize());
		}
		
	}
	
	
	private void bindActiveTexture(GL2 gl, int activeTexture) {
		switch(activeTexture) {
			case 0:
				gl.glActiveTexture(GL.GL_TEXTURE0);
				break;
			case 1:
				gl.glActiveTexture(GL.GL_TEXTURE1);
				break;
			case 2:
				gl.glActiveTexture(GL.GL_TEXTURE2);
				break;
			case 3:
				gl.glActiveTexture(GL.GL_TEXTURE3);
				break;
			case 4:
				gl.glActiveTexture(GL.GL_TEXTURE4);
				break;
			case 5:
				gl.glActiveTexture(GL.GL_TEXTURE5);
				break;
			case 6:
				gl.glActiveTexture(GL.GL_TEXTURE6);
				break;
			case 7:
				gl.glActiveTexture(GL.GL_TEXTURE7);
				break;
			case 8:
				gl.glActiveTexture(GL.GL_TEXTURE8);
				break;
			case 9:
				gl.glActiveTexture(GL.GL_TEXTURE9);
				break;
			case 10:
				gl.glActiveTexture(GL.GL_TEXTURE10);
				break;
			case 11:
				gl.glActiveTexture(GL.GL_TEXTURE11);
				break;
			case 12:
				gl.glActiveTexture(GL.GL_TEXTURE12);
				break;
			case 13:
				gl.glActiveTexture(GL.GL_TEXTURE13);
				break;
			case 14:
				gl.glActiveTexture(GL.GL_TEXTURE14);
				break;
			case 15:
				gl.glActiveTexture(GL.GL_TEXTURE15);
				break;
			case 16:
				gl.glActiveTexture(GL.GL_TEXTURE16);
				break;
			case 17:
				gl.glActiveTexture(GL.GL_TEXTURE17);
				break;
			case 18:
				gl.glActiveTexture(GL.GL_TEXTURE18);
				break;
			case 19:
				gl.glActiveTexture(GL.GL_TEXTURE19);
				break;
			case 20:
				gl.glActiveTexture(GL.GL_TEXTURE20);
				break;
			case 21:
				gl.glActiveTexture(GL.GL_TEXTURE21);
				break;
			case 22:
				gl.glActiveTexture(GL.GL_TEXTURE22);
				break;
			case 23:
				gl.glActiveTexture(GL.GL_TEXTURE23);
				break;
			case 24:
				gl.glActiveTexture(GL.GL_TEXTURE24);
				break;
			case 25:
				gl.glActiveTexture(GL.GL_TEXTURE25);
				break;
			case 26:
				gl.glActiveTexture(GL.GL_TEXTURE26);
				break;
			case 27:
				gl.glActiveTexture(GL.GL_TEXTURE27);
				break;
			case 28:
				gl.glActiveTexture(GL.GL_TEXTURE28);
				break;
			case 29:
				gl.glActiveTexture(GL.GL_TEXTURE29);
				break;
			case 30:
				gl.glActiveTexture(GL.GL_TEXTURE30);
				break;
			case 31:
				gl.glActiveTexture(GL.GL_TEXTURE31);
				break;
			
		}
	}
	
	
	
	private void clearShaderParams(ShaderObject shaderObject, Animation animation) {
//		shaderObject.getGl().glActiveTexture(0);
	}
	
	
	/**
	 * Renders all the entities.
	 * 
	 * @param The
	 *            GL implementation to use for rendering
	 */
	private void renderEntities(GL4bc gl){
		 for(Entity entity : this.entities) {
		    	gl.glEnableClientState( GL2.GL_VERTEX_ARRAY );
			    gl.glEnableClientState( GL2.GL_COLOR_ARRAY );

			    colorBuffer = entity.getVertexColors();
			    vertexBuffer = entity.getVertices();
			    
			    gl.glColorPointer(3, GL2.GL_FLOAT, 0, colorBuffer);
			    gl.glVertexPointer(3, GL2.GL_FLOAT, 0, vertexBuffer);
			    gl.glDrawArrays(GL2.GL_TRIANGLES, 0, entity.getVertexCount());
			    
			    gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
			    gl.glDisableClientState(GL2.GL_COLOR_ARRAY);
		 }
		 gl.glColor3f(0.5f, 0.5f, 0.5f);
	}
	
	@Override
	public void dispose(GLAutoDrawable arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public void init(GLAutoDrawable drawable) {
		drawable.getGL().setSwapInterval(1);
		drawable.getGL().glEnable(GL2.GL_DEPTH_TEST);
		this.activeCamera.init(canvas);
	}

	@Override
	public void reshape(final GLAutoDrawable drawable, final int x,
			final int y, final int width, final int height) {
		this.width = width;
		this.height = height;
	}

	@Override
	public Object getDrawableImplementation() {
		return this.drawableImplementation;
	}

	public void addEntity(Entity entity) {
		this.entities.add(entity);
	}

	public void addParticleSystem(ParticleSystem particleSystem) {
		this.particleSystems.add(particleSystem);
	}

	public void addCrowdSystem(CrowdSystem crowdSystem) {
		this.crowdSystems.add(crowdSystem);
	}

	public void addAsset(Asset asset) {
		this.assets.add(asset);
	}

	/**
	 * Obtains the camera that's being used to render.
	 * 
	 * @return The current active camera
	 */
	public Camera getCamera() {
		return activeCamera;
	}

	/**
	 * Adds a camera to the renderer's camera list
	 * 
	 * @param camera
	 *            The camera that will be added
	 */
	public void addCamera(Camera camera) {
		cameras.add(camera);
	}

	/**
	 * Adds a camera to the renderer's camera list and sets it as the active
	 * camera.
	 * 
	 * @param camera
	 *            The camera that will be added
	 */
	public void addAndSetCamera(Camera camera) {
		addCamera(camera);
		setCamera(camera);
	}

	/**
	 * Removes a camera from the renderer's camera list.
	 * 
	 * @param camera
	 *            The camera that will be removed
	 * @return True if the camera was removed, false otherwise.
	 */
	public boolean removeCamera(Camera camera) {
		return cameras.remove(camera);
	}

	/**
	 * Removes a camera from the renderer's camera list.
	 * 
	 * @param index
	 *            The index of the camera that will be removed.
	 * @return True if the camera was removed, false otherwise.
	 */
	public boolean removeCamera(int index) {
		if (index > 0 && index < cameras.size()) {
			cameras.remove(index);
			return true;
		}
		return false;
	}

	/**
	 * Sets a camera from the renderer's camera list as the active camera.
	 * 
	 * @param camera
	 *            The camera from the list that will be set as active.
	 */
	public void setCamera(Camera camera) {
		int index = cameras.indexOf(camera);
		if (index >= 0) {
			this.activeCamera = cameras.get(index);
		}
	}

	/**
	 * Sets a camera from the renderer's camera list as the active camera.
	 * 
	 * @param index
	 *            The index of the camera from the list that will be set as
	 *            active.
	 */
	public void setCamera(int index) {
		if (index > 0 && index < cameras.size()) {
			this.activeCamera = cameras.get(index);
		}
	}
}
